/*
 *  xbox360wireless_headset.cpp
 *
 *  Created by Delio Brignoli on 11/12/08.
 *  Copyright 2008 Delio Brignoli. All rights reserved.
 *
 */

#include <IOKit/IOLib.h>
#include <IOKit/usb/IOUSBDevice.h>
#include <IOKit/usb/IOUSBInterface.h>
#include "headset.h"
#include "debug.h"

extern "C" {
#include <pexpert/pexpert.h>//This is for debugging purposes ONLY
}

// Define my superclass
#define super IOAudioDevice

OSDefineMetaClassAndStructors(WHeadset, IOAudioDevice)

/*
WGRManager* WHeadset::findManager(int index) {
	WGRManager *result = NULL;
	IOUSBFindInterfaceRequest findInterfaceRequest;

	//Fill in the request information
	findInterfaceRequest.bInterfaceClass = 0xFF; //Vendor specific
	findInterfaceRequest.bInterfaceSubClass = 0x5D;
	findInterfaceRequest.bInterfaceProtocol = 0x81;
	findInterfaceRequest.bAlternateSetting = 0x00;

	if (IOUSBDevice *device = OSDynamicCast(IOUSBDevice, this->getProvider()->getProvider())) {
		IOUSBInterface *interface = device->FindNextInterface(NULL, &findInterfaceRequest);
		if (interface) {
			do {
				if (interface->GetInterfaceNumber() == (2*index)) {
					result = OSDynamicCast(WGRManager, interface->getClient());
					if(!result)
						dIOLog(INFO, "Interface %d client is either NULL or of the wrong type\n", interface->GetInterfaceNumber());
					break;
				}
			} while(interface = device->FindNextInterface(interface, &findInterfaceRequest));
		} else {
			dIOLog(INFO, "No matching interface for our criteria (%d,%d,%d,%d).\n", findInterfaceRequest.bInterfaceClass,
				  findInterfaceRequest.bInterfaceSubClass,
				  findInterfaceRequest.bInterfaceProtocol,
				  findInterfaceRequest.bAlternateSetting);
		}
	} else {
		dIOLog(INFO, "Could not find the parent IOUSBDevice for index = %d\n", index);
	}
	return result;
}
*/

WGRManager* WHeadset::findManager(int headsetIdx) {
	WGRManager *result = NULL;
	OSString *mngrName = NULL;
	OSIterator *iter = NULL;

	//make sure the headsetindex string representation is one char long
	assert(headsetIdx >= 0 && headsetIdx <= 9);
	mngrName = OSString::withCString(stringify(WGRManager) "_X");
	if (!mngrName) {
		dIOLog(ERROR, "Resource name string allocation failed.\n");
		goto Done;
	}
	mngrName->setChar('1' + (char) headsetIdx, mngrName->getLength()-1);
	
	dIOLog(INFO, "Attempting match for XBox Wireless Gaming Manager.\n");
	OSDictionary *matchingDict = IOService::nameMatching(mngrName->getCStringNoCopy());

	iter = getMatchingServices(matchingDict);
	if (!iter) {
		dIOLog(INFO, "Match for %s not found. Exiting.\n", mngrName->getCStringNoCopy());
		goto Done;
	}
	
	while(OSObject *obj = iter->getNextObject()) {
		result = OSDynamicCast(WGRManager, obj);
		//We pick the first one
		break;
	}
	
Done:
	if (iter)
		iter->release();
	
	if (mngrName)
		mngrName->release();
	
	return result;
}

bool WHeadset::initHardware(IOService *provider)
{
	bool result = false;

	headsetIdx = -1;
	outstandingIOCount = 0;
	usbInterface - NULL;
	inPipe = NULL;
	outPipe = NULL;
	serviceName = NULL;
	managerService = NULL;
	mach_timespec_t timeout = {
		tv_sec: 1,
		tv_nsec: 0
	};
	
    if (!super::initHardware(provider)) {
        goto Done;
    }
	
    usbInterface = OSDynamicCast(IOUSBInterface, provider);
    if (!usbInterface) {
        goto Done;
    }

	if (!usbInterface->open(this)) {
		dIOLog(ERROR, "Could not open the USB interface. Exiting.\n");
        goto Done;	
	}
	
	headsetIdx = ((int)usbInterface->GetInterfaceNumber())/2;
	assert(headsetIdx >= 0 && headsetIdx <= 9);
	serviceName = OSString::withCString(stringify(WHeadset) "_X");
	if (!serviceName) {
		dIOLog(ERROR, "Resource name string allocation failed.\n");
		goto Done;
	}
	serviceName->setChar('1' + (char) headsetIdx, serviceName->getLength()-1);
	setName(serviceName->getCStringNoCopy());
	
	managerService = this->findManager(headsetIdx);
	if (!managerService) {
		dIOLog(INFO, "Cannot find the wireless gaming receiver manager for headset number %d. exiting\n", headsetIdx);
		goto Done;
	}
	
	managerService->retain();

	dIOLog(INFO, "Waiting for XBox Wireless Gaming Manager to become available.\n");
	if (managerService->waitQuiet(&timeout) != kIOReturnSuccess) {
		dIOLog(INFO, "Timeout while waiting for service to become available. Exiting.\n");
		goto Done;	
	}
	
	managerService->open(this);
	
	if (!managerService->isHeadsetReady()) {
		dIOLog(INFO, "Headset number %d is not ready. exiting\n", headsetIdx);
		goto Done;
	}
	
	managerService->registerHeadsetDriver(this);

	IOUSBFindEndpointRequest endpointRequest;
	
	//Find interrupt in pipe
	endpointRequest.type =	kUSBInterrupt;
	endpointRequest.direction = kUSBIn;
	endpointRequest.maxPacketSize = 0;
	endpointRequest.interval = 0;
	inPipe = usbInterface->FindNextPipe(NULL, &endpointRequest);
	if (!inPipe) {
		dIOLog(ERROR, "Could not find the interrupt in pipe. Exiting.\n");
		goto Done;
	} else {
		inPipe->retain();
	}
	
	//Find interrupt out pipe
	endpointRequest.type =	kUSBInterrupt;
	endpointRequest.direction = kUSBOut;
	endpointRequest.maxPacketSize = 0;
	endpointRequest.interval = 0;
	outPipe = usbInterface->FindNextPipe(NULL, &endpointRequest);
	if (!outPipe) {
		dIOLog(ERROR, "Could not find the interrupt out pipe. Exiting.\n");
		goto Done;
	} else {
		outPipe->retain();
	}
	
	setDeviceName("Xbox 360 Wireless Headset");
    setDeviceShortName("Headset");
    setManufacturerName("Microsoft");
	
	result = true;

	dIOLog(INFO, "Heaset driver started.\n");

Done:	
    if (!result) {
		cleanUp(provider);
    }
	
    return result;
}

void WHeadset::stop(IOService *provider)
{
	cleanUp(provider);
    super::stop(provider);
}

bool WHeadset::willTerminate(IOService * provider, IOOptionBits options) {	
	if (inPipe) {
		inPipe->Abort();
	}
	
	//We could probably let the outstanding writes run to completion, but we abort them just to be sure..
	if (outPipe) {
		outPipe->Abort();
	}
	
	return super::willTerminate(provider, options);
}

bool WHeadset::didTerminate( IOService * provider, IOOptionBits options, bool * defer ) {
	if (outstandingIOCount == 0 && usbInterface) {
		usbInterface->close(this);
		usbInterface = NULL;
    }
	
    return super::didTerminate(provider, options, defer);
}

void WHeadset::cleanUp(IOService *provider) {

	if (serviceName) {
		serviceName->release();
		serviceName = NULL;
	}
	
	if (managerService) {
		if (managerService->isOpen(this))
			managerService->registerHeadsetDriver(NULL);
			managerService->close(this);
		managerService->release();
		managerService = NULL;
	}
	
	if (inPipe) {
		inPipe->release();
		inPipe = NULL;
	}
	
	if (outPipe) {
		outPipe->release();
		outPipe = NULL;
	}

	if (usbInterface) {
		usbInterface->close(this);
		usbInterface = NULL;
    }
	
Done:
	assert(outstandingIOCount == 0);
}
